import java.io.IOException;
import java.util.List;
import java.util.Hashtable;
import java.util.ArrayList;
import java.io.BufferedReader;
import java.io.InputStreamReader;





public class Dvonn4ever {
	
	public static void main(String argsp[]){
		try {
			Hasher.register(new long[Param.SIZE][Param.SIZE][2]);
			MessageManager.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
}


 abstract class AI {
	
	private byte color;
	private Board board;
	private Evaluator evaluator;
	
	 static AI create(String className, Evaluator evaluator, byte color, Board board) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
		Class<?> c = Class.forName(className);
		if (!AI.class.isAssignableFrom(c))
			throw new RuntimeException("Invalid AI subclass");
		return ((AI) c.newInstance()).createAI(evaluator, color, board);
	}
	
	protected abstract AI createAI(Evaluator evaluator, byte color, Board board);
	public abstract Move getMove(Board board, byte color);
	public abstract String getName();
	public abstract void play(Move move);
	
	public AI(){}
	
	protected AI(Evaluator evaluator, byte color, Board board){
		this.evaluator = evaluator;
		this.color = color;
		this.board = board;
	}
	
	public float fe(Board board,byte color){
		return evaluator.evaluate(board, color);
	}
	
	public String toString(){
		return "AI : \n\t-name : "+getName()+"\n\t-evaluator : "+evaluator+"\n\t-color : "+color;
	}

	public byte getColor() {
		return color;
	}

	public void setColor(byte color) {
		this.color = color;
	}

	public Board getBoard() {
		return board;
	}

	public void setBoard(Board board) {
		this.board = board;
	}
}




 class RandomAI extends AI {
	
	
	public RandomAI(){}
	
	protected RandomAI(Evaluator evaluator, byte color, Board board) {
		super(evaluator,color,board);
	}

	@Override
	public Move getMove(Board board, byte color) {
		List<Move> moves = board.getValidMoves(color);
		return moves.get((int)((moves.size()-1)*Math.random()));
	}

	@Override
	public String getName() {
		return "RANDOM";
	}

	@Override
	protected AI createAI(Evaluator evaluator, byte color, Board board) {
		return new RandomAI(evaluator,color,board);
	}

	@Override
	public void play(Move move) {
		setBoard(getBoard().doMove(move));
	}

}


 class DummyEvaluator extends Evaluator {

	@Override
	protected float fe(Board board, byte color) {
		float[] score = board.score();
		return score[1]-score[0];
	}

	@Override
	public String toString() {
		return "DUMMY";
	}

}



 abstract class Evaluator {
	private static final Hashtable<Long,Float> cache = new Hashtable<Long,Float>();
	
	public float evaluate(Board board, byte color){
		if(Param.WITH_CACHE){
			Float score = cache.get(board.getHash());
			if(score == null){
				score = fe(board,color);
				cache.put(board.getHash(), score);
			}
			return score;
		}
		
		return fe(board,color);
	}
	
	protected abstract float fe(Board board, byte color);
	public abstract String toString();
}


 class Edge {
	public Move move;
	public Node node;
	
	public Edge(Move move, Node node){
		this.move = move;
		this.node = node;
	}
	
}


 class Evaluation {
	public Move move;
	public float evaluation;
	
	public Evaluation(Move move, float evaluation){
		this.move = move;
		this.evaluation = evaluation;
	}
	
}





 class Node {

	public static int LEVEL_MAX;
	private static AI ai = null;

	private int level;
	private ArrayList<Edge> moves;
	private float nodeAB;

	// 0 if Node is not terminal or if Evaluation Function hasn't been called
	// yet.
	// +1 if it is terminal and already has been computed for white.
	// -1 if it is terminal and already has been computed for black.
	byte nodeABLastComputedWith;
	boolean isTerminal;
	
	
	public static void registerAI(AI ai){
		Node.ai = ai;
	}
	
	

	public Node(int level) {
		this.level = level;
	}

	public ArrayList<Evaluation> getBestMoves() {
		if (!Param.TIME_OPTIMIZATION_ENABLED)
			reshuffle(0, moves.size());

		ArrayList<Evaluation> answer = new ArrayList<Evaluation>();

		if (moves.size() > 0) {
			for (int i = 0; i < moves.size(); i++)
				answer.add(new Evaluation(moves.get(i).move, moves.get(i).node
						.getNodeAB()));
		} else {
			Logger.logln(Node.class,"ERROR:: Move selection unavailable because Memory depth exceeded.");
			System.exit(0);
		}
		return answer;
	}

	public ArrayList<Edge> getBestNodes() {
		if (Param.TIME_OPTIMIZATION_ENABLED)
			return moves;
		else {
			reshuffle(0, moves.size());
			return moves;
		}
	}

	public float initSearch(Board board, byte color, double initTime) {

		if (moves.size() == 0) {
			ArrayList<Move> moveList = board
					.getValidMoves((byte) (isMax(0) ? color : color * -1));
			for (int i = 0; i < moveList.size(); i++)
				moves.add(new Edge(moveList.get(i), new Node(0)));
		}

		// Re-init fNodeAB
		nodeAB = isMax(0) ? Float.MIN_VALUE : Float.MAX_VALUE;

		/*
		 * For all the possible moves from this node we look for the max
		 * (respectively min)
		 */
		isTerminal = true;
		double duration;
		int imove;

		for (imove = 0; imove < moves.size(); imove++) {
			nodeAB = minMax(
					(moves.get(imove).node).getPathScore(board,
							moves.get(imove).move, nodeAB, 1, color, initTime),
					nodeAB, 0);
			isTerminal = (isTerminal && moves.get(imove).node.isTerminal());

			/*
			 * If time is over and if it's not the first time that we entered
			 * the search function (level max is > 2) then we should return...
			 */
			duration = ((double) System.nanoTime()) - initTime;
			if (Param.TIME_OPTIMIZATION_ENABLED && duration >= Param.TIME_LIMIT
					&& LEVEL_MAX > Param.INIT_DEPTH_SEARCH) {
				reshuffle(0, imove);
				Logger.logln(Node.class," Time out, leaving level :: 0 with "
						+ imove + " moves analysed.");
				return nodeAB;
			}

		}
		reshuffle(0, moves.size());
		return nodeAB;
	}

	private boolean isTerminal() {
		return isTerminal;
	}

	protected float getPathScore(Board board, Move move, float fatherAlpha,
			int level, byte Color, double initTime) {

		board.doMove(move);

		if (moves.size() == 0) {
			ArrayList<Move> moveList = board
					.getValidMoves((byte) (isMax(level) ? Color : (Color * -1)));
			for (int i = 0; i < moveList.size(); i++)
				moves.add(new Edge(moveList.get(i), new Node(level)));
		}

		// If we reach the level we want to check we just have to return the
		// board evaluation
		if (board.isOver()
				|| ((level >= Param.MEM_LEVEL_MAX) && moves.size() > Param.N_MOVES_LIMIT)) {
			isTerminal = board.isOver();
			if (nodeABLastComputedWith != Color) {
				nodeABLastComputedWith = Color;
				nodeAB = ai.fe(board, Color);
			}
			return nodeAB;
		}

		// Init with high/low values so the min/max will work
		float alpha, beta;
		float oldNodeAB;

		nodeAB = isMax(level) ? Float.MIN_VALUE : Float.MAX_VALUE;

		/*
		 * For all the possible moves from this node we look for the max
		 * (respectively min)
		 */
		isTerminal = true;
		int imove = 0;
		double duration;
		for (imove = 0; imove < moves.size(); imove++) {
			oldNodeAB = nodeAB;
			nodeAB = minMax(
					(moves.get(imove).node).getPathScore(board,
							moves.get(imove).move, nodeAB, (level + 1), Color,
							initTime), nodeAB, level);
			isTerminal = (isTerminal && moves.get(imove).node.isTerminal());
			if (nodeAB != oldNodeAB) {
				alpha = isMax(level) ? nodeAB : fatherAlpha;
				beta = isMax(level) ? fatherAlpha : nodeAB;

				// If alpha >= beta then there is no need to check this node
				// anymore...
				if (alpha > beta && Param.DO_PRUNING) {
					reshuffle(level, moves.size());
					return nodeAB;
				}
			}

			/*
			 * If time is over, and it is not the maxlevel-1 situation and if
			 * it's not the first time that we entered the search function
			 * (level max is > 2) then we should return...
			 */
			duration = ((double) System.nanoTime()) - initTime;
			if (Param.TIME_OPTIMIZATION_ENABLED
					&& duration >= (Param.TIME_LIMIT)
					&& (level + 1) < LEVEL_MAX
					&& LEVEL_MAX > Param.INIT_DEPTH_SEARCH) {
				reshuffle(level, imove);
				Logger.logln(Node.class," Time out, leaving level :: " + level
						+ " with " + imove + " moves analysed.");
				return nodeAB;
			}

		}
		reshuffle(level, moves.size());
		return nodeAB;
	}

	private void reshuffle(int fLevel, int size) {
		if (level > Param.MEM_LEVEL_MAX)
			moves.clear();
		else
			quickSort(size);

	}

	public Node getNodeForMove(Move move) {
		for (int i = 0; i < moves.size(); i++) {
			if (moves.get(i).move.equals(move))
				return moves.get(i).node;
		}

		if (moves.size() != 0)
			Logger.logln(Node.class,"WARNING:: A Node has been asked for a move that was not considered. move="
							+ move);
		else
			Logger.logln(Node.class,"WARNING:: A Node has been asked for a move but it had exceeded memorization depth limit. ");
		return new Node(0);
	}

	void printTree() {
		for (int i = 0; i < moves.size(); i++)
			moves.get(i).node.printNodes(0, moves.get(i).move);
	}

	private void printNodes(int fLevel, Move move) {
		if (nodeAB < Float.MAX_VALUE && nodeAB > Float.MIN_VALUE) {
			for (int i = 0; i < fLevel; i++)
				Logger.log(Node.class,"-");
			Logger.log(Node.class,move + " : " + nodeAB
					+ (isTerminal ? " : Terminal" : " : No Terminal\n"));

			for (int i = 0; i < moves.size(); i++)
				moves.get(i).node.printNodes(fLevel + 1, moves.get(i).move);
		}
		Logger.flush();
	}

	public float getNodeAB() {
		return nodeAB;
	}

	public float minMax(float a, float b, int fLevel) {
		return isMax(level) ? Math.max(a, b) : Math.min(a, b);
	}

	boolean isMax(int fLevel) {
		return (fLevel % 2) == 0;
	}

	private void quickSort(int size) {
		int[] beg = new int[Param.QS_MAX_LEVELS];
		int[] end = new int[Param.QS_MAX_LEVELS];
		int i = 0, L, R;
		Edge piv = moves.get(0);
		beg[0] = 0;
		end[0] = size;
		while (i >= 0) {
			L = beg[i];
			R = end[i] - 1;
			if (L < R) {
				piv = moves.get(L);
				while (L < R) {
					while (moves.get(R).node.getNodeAB() <= piv.node
							.getNodeAB() && L < R)
						R--;
					if (L < R)
						moves.set(L++, moves.get(R));
					while (moves.get(L).node.getNodeAB() >= piv.node
							.getNodeAB() && L < R)
						L++;
					if (L < R)
						moves.set(R--, moves.get(L));
				}
				moves.set(L, piv);
				beg[i + 1] = L + 1;
				end[i + 1] = end[i];
				end[i++] = L;
			} else {
				i--;
			}
		}
	}

}


 class Hasher {
	
	private static Hasher instance;
	
	final long[][][] bitStrings;
	
	public static Hasher register(long[][][] bitStrings){
		if(instance != null)
			throw new RuntimeException("Hasher already registred");
		instance = new Hasher(bitStrings);
		return instance;
	}
	

	/*
	 * (rotate)Zp(z) =z2, z3, z4, z1, z6, z7, z8, z5
	 * (mirror)Zu(z) =z4, z3, z2, z1, z8, z7, z6, z5
	 * (color) Zk(z) =z5, z6, z7, z8, z1, z2, z3, z4.
	 */
	final static byte[] rotateShift = new byte[]{1,2,3,0,5,6,7,4};
	final static byte[] mirrrorShift = new byte[]{3,2,1,0,7,6,5,4};
	final static byte[] colorShift = new byte[]{4,5,6,7,0,1,2,3};
	
	public static Hasher getInstance(){
		if(instance == null)
			throw new RuntimeException("Hasher not registred");
		return instance;
	}
	
	private Hasher(long[][][] bitStrings){
		this.bitStrings = bitStrings;
	}
	
	
	public long getBitString(Coordinate where, byte color) {
		return bitStrings[where.x][where.y][(color+1)/2];
	}
	
	private long shiftByte(final long toShift, final byte from, final byte to){
		return ((toShift&((0xFF)<<(8*from)))>>(8*from))<<(8*to);
	}
	
	private long getShiftedHash(Long bitString, byte[] indices){
		long value  = bitString.longValue();
		long result = 0;
		for(byte i = 0; i<indices.length; i++)
			result = result | shiftByte(value,i,indices[i]);
		
		return result;
	}
	
	/**
	 * Return the mirrored hash
	 * @param bitString The original hash 
	 * @return The mirrored hash
	 */
	public long getMirroredHash(long bitString) {
		return getShiftedHash(bitString, mirrrorShift);
	}
	
	/**
	 * Return the rotated hash n times
	 * @param bitString The original hash 
	 * @param n The number of rotation 
	 * @return The hash after n rotation
	 */
	public long getRotatedHash(long bitString, byte n) {
		for(int i = 0; i<n;i++)
			bitString = getShiftedHash(bitString,rotateShift);
		
		return bitString;
	}
	
	/**
	 * Return the hash after inversion of the color
	 * @param bitString The original hash 
	 * @return The mirrored hash
	 */
	public long getInvereColorHash(long bitString) {
		return getShiftedHash(bitString, colorShift);
	}
	
	
	public boolean equals(long a, long b) {
		return a==b;
	}
	
	public long hash(Board board, Move move){
		return 0;
	}
}

 class Logger {
	private static long startingTime;
	private static boolean starting;
	
	public static void init(){
		startingTime = System.nanoTime();
		starting = true;
		try {
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		logln(Logger.class,"Logger ready...");
	}
	
	public static void log(Class<?> c, String message){
		if(starting){
			starting = false;
			System.err.println((System.nanoTime()-startingTime)+" : "+c.getName()+" : \n"+message);
		} else {
			System.err.println(message);
		}
	}
	
	public static void flush(){
		starting = true;
		System.err.println();
	}
	
	public static void logln(Class<?> c, String message){
		if(!starting){
			starting = true;
			System.err.println();
		}
			
		System.err.println((System.nanoTime()-startingTime)+" : "+c.getName()+" : \n"+message);
	}
	
}




 class MessageManager {
	
	private static AI ai = null;
	
	public static void start() throws IOException{
		Logger.init();
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		String line;
		while((line=reader.readLine())!=null){
			digestMessage(line);
		}
	}
	
	public static void sendMessage(String message){
		System.out.println(message);
		System.out.flush();
	}
	
	private static void digestMessage(String message){
		if(message.equals("Quit")){
			System.exit(0);
		} else if(message.equals("Start")){
			init((byte)-1);
		} else {
			if(ai == null)
				init((byte)1);
			ai.play(new Move(message, (byte)(ai.getColor()*-1)));
		}
		
		//aiPlay();
		
		Logger.logln(ai.getBoard().getClass(), ai.getBoard().toString());
	}
	
	private static void aiPlay(){
		Move move = ai.getMove(ai.getBoard(), ai.getColor());
		ai.play(move);
		sendMessage(move.toString());
	}
	
	private static void init(byte color){
		try {
			ai = AI.create(Param.AI, new DummyEvaluator(), color, new SympleBoard(Param.SIZE));
			Logger.logln(ai.getClass(), ai.toString());
		} catch (InstantiationException e) {
			Logger.logln(MessageManager.class, e.toString());
		} catch (IllegalAccessException e) {
			Logger.logln(MessageManager.class, e.toString());
		} catch (ClassNotFoundException e) {
			Logger.logln(MessageManager.class, e.toString());
		}
	}
}




 abstract class Board {
	
	final protected byte[] config;
	private long hash;
	
	public abstract float[] score();
	public abstract boolean isOver();
	protected abstract void move(Move move);
	public abstract ArrayList<Move> getValidMoves(byte color);
	public abstract boolean isValid(Move move);
	public abstract Board clone();
	public abstract String toString();
	
	public Board(int size){
		this.config = new byte[size*size];
		hash = 0;
	}
	
	public Board(byte[] config, long hash){
		this.config = config;
		this.hash = hash;
	}
	
	
	public Board doMove(Move move){
		Board board = this.clone();
		board.move(move);
		board.setHash(Hasher.getInstance().hash(this, move));
		return board;
	}

	private void setHash(long hash) {
		this.hash = hash;
	}
	
	public byte[] getConfig() {
		return config;
	}
	
	public byte colorAt(Coordinate c){
		return config[Param.SIZE*c.y + c.x];
	}

	public long getHash() {
		return hash;
	}
	
	
}





 class Chaine {
	private byte color;
	private ArrayList<Coordinate> stones = new ArrayList<Coordinate>();
	private ArrayList<Coordinate> liberties = new ArrayList<Coordinate>();
	
	public void addStone(Coordinate c){
		stones.add(c);
	}
	
	public byte getColor(){
		return color;
	}
	
	public int size(){
		return stones.size();
	}
	
	public boolean isLibertyOf(Coordinate n){
		return liberties.indexOf(n) != -1;
	}
	
	public void addLiberty(Coordinate n){
		if(!isLibertyOf(n))
			liberties.add(n);
	}
	
	public void setLiberties(ArrayList<Coordinate> liberties){
		this.liberties = liberties;
	}
	
	public static Chaine merge(ArrayList<Chaine> chaines, SympleBoard board){
		Chaine r = new Chaine();
		Chaine a;
		for(int i = 0; i<chaines.size(); i++){
			a = chaines.get(i);
			for(Coordinate stone : a.stones){
				r.stones.add(stone);
				board.setChaineAt(r, stone);
			}
			
			for(Coordinate c : a.liberties){
				r.addLiberty(c);
			}
			
			board.removeChaine(a);
		}
		
		board.addChaine(r);
		
		return r;
	}
	
	public Chaine cloneTo(SympleBoard board){
		Chaine chaine = new Chaine();
		chaine.color = color;
		ArrayList<Coordinate> stones = new ArrayList<Coordinate>();
		
		for(Coordinate c : this.stones){
			stones.add(c);
			board.setChaineAt(chaine, c);
		}
		
		chaine.stones = stones;
		
		ArrayList<Coordinate> liberties = new ArrayList<Coordinate>();
		for(Coordinate c : this.liberties)
			liberties.add(c);
		
		chaine.liberties = liberties;
		
		return chaine;
	}

	public void removeLiberty(Coordinate c) {
		liberties.remove(c);
	}

	public ArrayList<Coordinate> getLiberties() {
		return liberties;
	}
}



 class Coordinate {
	
	public byte x;
	public byte y;
	
	public Coordinate(String coord){
		x = (byte)((byte)coord.charAt(0) - 65);
		y = (byte)(Byte.parseByte(coord.substring(1))-1);
	}
	
	public Coordinate(byte x, byte y){
		this.x = x;
		this.y = y;
	}
	
	public boolean equals(Object o){
		Coordinate c = (Coordinate) o;
		return x == c.x && y == c.y;
	}
	
	public ArrayList<Coordinate> getNeighbours() {
		ArrayList<Coordinate> neighbours = new ArrayList<Coordinate>();

		if (x - 1 >= 0)
			neighbours.add(new Coordinate((byte) (x - 1), y));

		if (y - 1 >= 0)
			neighbours.add(new Coordinate(x, (byte) (y - 1)));

		if (x + 1 < Param.SIZE)
			neighbours.add(new Coordinate((byte) (x + 1), y));

		if (y + 1 < Param.SIZE)
			neighbours.add(new Coordinate(x, (byte) (y + 1)));

		return neighbours;
	}
	
	public String toString(){
		return Character.toString((char)(x+65))+Byte.toString((byte)(y+1));
	}
}


 class Move {
	
	private ArrayList<Coordinate> stones;
	private byte color;
	
	public Move(String move, byte color){
		String[] coords = move.split("-");
		
		stones = new ArrayList<Coordinate>();
		for(String c : coords)
			stones.add(new Coordinate(c));
		
		this.color = color;
		
	}
	
	public Move(Coordinate c, byte color){
		stones =  new ArrayList<Coordinate>();
		stones.add(new Coordinate(c.x,c.y));
		this.color = color;
	}
	
	public Move(ArrayList<Coordinate> stones, byte color){
		stones =  new ArrayList<Coordinate>();
		for(Coordinate c : stones)
			this.stones.add(new Coordinate(c.x,c.y));
		this.color = color;
	}
	
	public byte getColor(){
		return color;
	}
	
	public ArrayList<Coordinate> getStones(){
		return stones;
	}
	
	
	public String toString(){
		String move = stones.get(0).toString();
		for(int i = 1; i<stones.size();i++)
			move += "-"+stones.get(i);
		
		return move;
	}
}




 class SympleBoard extends Board{
	
	final private Chaine[] chaines;
	final private ArrayList<Chaine> lChaines;

	public SympleBoard(byte[] config, Chaine[] chaines, long hash) {
		super(config, hash);
		this.chaines = chaines;
		lChaines = new ArrayList<Chaine>();
	}
	
	public SympleBoard(int size) {
		super(size);
		this.chaines = new Chaine[size*size];
		lChaines = new ArrayList<Chaine>();
	}

	@Override
	public float[] score() {
		float[] score = new float[2];
		
		for(Chaine c : lChaines){
			score[(c.getColor()+1)/2] = c.size()-6;
		}
		
		if(score[0] < 0)
			score[0] = 0;
		
		if(score[1] < 0)
			score[1] = 0;
		
		return score;
	}

	@Override
	public boolean isOver() {
		//TODO : Might be faster if we store the empty point
		for(int x = 0; x<config.length; x++)
				if(config[x] == 0)
					return false;
		return true;
	}

	@Override
	protected void move(Move move) {
		ArrayList<Coordinate> coords = move.getStones();
		ArrayList<Coordinate> nes;
		ArrayList<Chaine> toMerge = new ArrayList<Chaine>();
		Chaine chaine;
		
		Logger.logln(this.getClass(), move+" "+move.getStones().get(0).x+" "+move.getStones().get(0).y);
		
		for(Coordinate c : coords){
			config[Param.SIZE*c.y + c.x] = move.getColor();
			
			toMerge.clear();
			
			chaine = new Chaine();
			chaine.addStone(c);
			chaine.setLiberties(getLiberties(c));
			
			toMerge.add(chaine);
			
			nes = c.getNeighbours();
			
			for(Coordinate n : nes){
				if(config[Param.SIZE*n.y + n.x] == move.getColor() && toMerge.indexOf(chaines[Param.SIZE*n.y + n.x]) == -1)
					toMerge.add(chaines[Param.SIZE*n.y + n.x]);
				else if(config[Param.SIZE*n.y + n.x] == -1*move.getColor() && chaines[Param.SIZE*n.y + n.x].isLibertyOf(n))
					chaines[Param.SIZE*n.y + n.x].removeLiberty(n);
			}
			
			Chaine.merge(toMerge, this);
			
		}
		
		
	}
	
	public void removeChaine(Chaine chaine){
		lChaines.remove(chaine);
	}
	
	public ArrayList<Coordinate> getLiberties(Coordinate c){
		ArrayList<Coordinate> nes = c.getNeighbours();
		ArrayList<Coordinate> liberties = new ArrayList<Coordinate>();
		for(Coordinate n : nes)
			if(config[Param.SIZE*n.y + n.x] == 0)
				liberties.add(n);
		
		return liberties;
				
	}

	@Override
	public ArrayList<Move> getValidMoves(byte color) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isValid(Move move) {
		// TODO Auto-generated method stub
		return false;
	}

	public Chaine[] getChaines() {
		return chaines;
	}
	
	public void addChaine(Chaine chaine) {
		lChaines.add(chaine);
	}
	
	public void setChaineAt(Chaine chaine, Coordinate c){
		chaines[Param.SIZE*c.y + c.x] = chaine;
	}

	@Override
	public Board clone() {
		SympleBoard board = new SympleBoard(Param.SIZE);
		
		System.arraycopy(config, 0, board.config, 0, config.length);
		 
		for(Chaine chaine : this.lChaines){
			board.lChaines.add(chaine.cloneTo(board));
		}
		
		return board;
	}

	@Override
	public String toString() {
		String board = "";
		for(int y = 0; y<Param.SIZE; y++){
			for(int x = 0; x<Param.SIZE; x++){
				if(config[y*Param.SIZE+x] == 0)
					board += " .";
				if(config[y*Param.SIZE+x] == -1)
					board += " x";
				if(config[y*Param.SIZE+x] == 1)
					board += " o";
			}
			board += "\n";
		}
		board += "\n\n";
		
		ArrayList<Coordinate> nes;
		boolean isLiberty = false;
		Coordinate tmp;
		for(int y = 0; y<Param.SIZE; y++){
			for(int x = 0; x<Param.SIZE; x++){
				if(config[y*Param.SIZE+x] == 0){
					tmp = new Coordinate((byte)x,(byte)y);
					isLiberty = false;
					for(Chaine chaine : lChaines){
						isLiberty = isLiberty || chaine.isLibertyOf(tmp);
					}
					if(isLiberty)
						board += " *";
					else
						board += " .";
				}else{
					board += " "+lChaines.indexOf(chaines[y*Param.SIZE+x]);
				}
			}
			board += "\n";
		}
		
		return board;
	}
	
}

 interface Param {
	
	//GENERAL
	public static int SIZE = 15; //Board size
	
	//AI
	public static String AI = "RandomAI";
	
	//MEMORY
	public static boolean WITH_CACHE = false; //Move cache (with hash) 
	public static int MEM_LEVEL_MAX = 5;
	public static int QS_MAX_LEVELS = 1000; //Max number of move in a node
	
	//MANAGE TIME
	public static boolean TIME_OPTIMIZATION_ENABLED = true;
	public static long TIME_LIMIT = 1000;
	public static int INIT_DEPTH_SEARCH = 1;
	public static int N_MOVES_LIMIT = 10;
	public static boolean DO_PRUNING = true;
}

